package lockd_client;

#########################################################################
# This code is based on daemon_connect but is specific to sending	#
# reqeusts to a lock daemon on a specified host. Note: This client can	#
# work on blocking/non-blocking mode for flexibility reasons. If not	#
# specified the default will be blocking with a infitite timeout.	#
#########################################################################

use strict;
use Sys::Hostname;
use cldb;
use clstatdb;
use C_client2;

{
my $error_msg=undef;	# hide var via closure

sub error {
	return $error_msg;
}

sub set_error {
	$error_msg=$_[0];
}
}

# arguments:
# host - required
# [validate]- check daemon is available
# [timeout] - timeout to get a lock [defaults to 10 seconds]
# [configdb] - the configuration database to use

sub new {
my $self={};
my $class=shift;
my %args=@_;

	#################################################################
	# Validated expected arguments are present, and abort if not.	#
	#################################################################

	set_error(undef);
	$args{validate}=0 if !exists($args{validate});
	$args{timeout}=10 if !exists($args{timeout});
	$args{configdb}="$::ENV{TRUECL_CFG}/config.db" if !exists($args{configdb});

	if(!exists($args{host}) || !defined($args{host})) {
		set_error("Missing mandatory 'host' argument.");
		return undef;
	}

	#################################################################
	# Validate the daemon is one that is known.			#
	#################################################################
	$args{daemon}="lha_lockd";
	my $x=$args{daemon};
	my $node=$args{host};

	#################################################################
	# We have a valid daemon, so get the port and list of IP	#
	# addresses for it.						#
	#################################################################

	my ($error_rc,$error_msg);
	my $db=new cldb($args{configdb});
	if(!defined($db)) {
		($error_rc,$error_msg)=cldb::error;
		set_error("Unable to open database: $error_msg");
		return undef;
	}

	my $href=$db->get_rows("SELECT number FROM ports WHERE detail = '$x';");
	if(!defined($href) || !defined($href->[0])) {
		set_error("Port not found for '$x' in config database.");
		$db->disconnect;
		return undef;
	}

	$self->{configdb}=$args{configdb};
	$self->{node}=$node;
	$self->{port}=$href->[0]->{number};
	$self->{daemon}=$x;
	$self->{timeout}=$args{timeout};

	$href=$db->get_rows("SELECT ips FROM nodes WHERE name = '$node';");
	if(!defined($href) || !defined($href->[0])) {
		set_error("Node '$node' not found in config database.");
		$db->disconnect;
		return undef;
	}

	$self->{ips}=$href->[0]->{ips};
	$db->disconnect;
	$db=undef;

	#################################################################
	# Have all the necessary information to attempt a connection,	#
	# so do so now.							#
	#################################################################

	my $cli=new C_client2(SERVER_NAME => $node, SERVER_INET => $self->{ips},
				SERVER_PORT => $self->{port}, PKI => $::PKI);

	if(!defined($cli)) {
		set_error("Daemon '$x' not running on '$node.'");
		return undef;
	}
	$self->{connection}=$cli;
	$cli->set_params(TIMEOUT => $args{timeout});
	bless($self,$class);
	if($args{validate}) {
		my $r=validate_running($self);
		if(!$r) {
			return undef;
		}
	}
	return $self;
}

sub validate_running {
my $self=shift;
my %args=@_;

	#################################################################
	# Send the daemon a PING request and if get a response we know	#
	# it is still running.						#
	#################################################################
	set_error(undef);
	my $cli=$self->{connection};
	my ($resp,$err)=$cli->send_request("PING");
	if(!defined($resp)) {
		set_error($err);
		return 0;
	} else {
		return 1;
	}
}

#########################################################################
# Attempt to acquire a specified lock. There are various mandatory and	#
# optional arguments.							#
#									#
# LOCK		The name of the lock to acquire.			#
# [WAITFOR]	The maximum amount of time to wait [in seconds]. 	#
#		0 = wait until the lock becomes available, or		#
#		-1 = non-block [return immediately even if no lock]	#
#		If not specified 0 is assumed.				#
#									#
# Return tuple:								#
# (0,undef)	Lock assigned successfully.				#
# (-1,msg)	Could not assign lock in WAITFOR seconds.		#
# (-2,undef)	Lock not available and WAITFOR=-1			#
# (-3,msg)	Other error [such as no such lock etc]			#
#									#
#########################################################################

sub get_lock {
my $self=shift;
my %args=@_;
my $resp="";
my $err="";

my $lock=undef;
my $waitfor=0;

	if(!exists($args{LOCK}) || !defined($args{LOCK})) {
		return(-3,"Mandatory LOCK argument not sent, or not defined.");
	}
	$lock=$args{LOCK};
	delete $args{LOCK};
	if(exists($args{WAITFOR}) && defined($args{WAITFOR})) {
		$waitfor=int($args{WAITFOR});
		delete $args{WAITFOR};
	}
	my $msg="GET_LOCK $lock " . hostname;
	my $timeout=3;
	my $oneway=0;
	if(%args) {
		return(-3,"Unexpected arguments found: " . join(",",keys(%args)));
	}

	set_error(undef);
	my $cli=$self->{connection};
	$cli->set_params(TIMEOUT => $timeout);
	($resp,$err)=$cli->send_request($msg,$oneway);
	if(!defined($resp)) {
		# Did not get a response from the daemon so return err	#
		set_error($err);
		return(-3,$err);
	}

	#################################################################
	# Now the other bit - if a lock has been given then exit, else	#
	# wait for the amount of time to , or send a busy response	#
	# back if necessary.						#
	#################################################################

	my $ctime=time;
	while(1) {
		if($resp =~ /^OK/) {
			return(0,undef);
		}
		if($resp !~ /^ALREADY_LOCKED/) {
			set_error($resp);
			return(-3,$resp);
		}

		#########################################################
		# So if we get here we have ALREADY_LOCKED, so the	#
		# response depends on waitfor.				#
		#########################################################

		if($waitfor>0) {
			if(time-$ctime>$waitfor) {
				set_error("Could not assign lock in WAITFOR periods [$waitfor seconds]");
				return(-1,"Could not assign lock in WAITFOR periods [$waitfor seconds]");
			}
		}
		if($waitfor<0) {
			return(-2,undef);
		}

		#########################################################
		# If here waitfor == 0 or waited less than timeout.	#
		#########################################################
		select(undef,undef,undef,1);
		($resp,$err)=$cli->send_request($msg,$oneway);
		if(!defined($resp)) {
			# Did not get a response from the daemon so return err	#
			set_error($err);
			return(-3,$err);
		}
	}
}

#########################################################################
# Release a lock - mostly always returns true since don't really care	#
# if the lock daemon is not available, or we don't actually still own	#
# the lock [since it was force-released]				#
#									#
# LOCK		The name of the lock to release.			#
#									#
# Return tuple:								#
# (0,undef)	Lock assigned released.					#
# (-1,msg)	Could not release lock - see msg.			#
#									#
#########################################################################

sub release_lock {
my $self=shift;
my %args=@_;
my $resp="";
my $err="";

my $lock=undef;

	if(!exists($args{LOCK}) || !defined($args{LOCK})) {
		return(-3,"Mandatory LOCK argument not sent, or not defined.");
	}
	$lock=$args{LOCK};
	delete $args{LOCK};
	my $msg="DROP_LOCK $lock " . hostname;
	my $timeout=3;
	my $oneway=0;
	if(%args) {
		return(-3,"Unexpected arguments found: " . join(",",keys(%args)));
	}

	set_error(undef);
	my $cli=$self->{connection};
	$cli->set_params(TIMEOUT => $timeout);
	($resp,$err)=$cli->send_request($msg,$oneway);
	if(!defined($resp)) {
		# Did not get a response from the daemon so return err	#
		set_error($err);
		return(-1,$err);
	}

	if($resp =~ /^NOSUCHLOCK/) {
		set_error("Lock specified [$lock] does not exist.");
		return(-1,"Lock specified [$lock] does not exist.");
	}
	return(0,undef);
}

1;

